
var CV = {};
CV.ids={};
CV.id = function(id){
    return CV.ids[id];
}
CV.UID = 0;

CV.Tree = new Class({
    Implements: [Events, Options],
    options: {
        types: {
            folder: {
                openIcon: 'cv-tree-folder-open-icon',
                closeIcon: 'cv-tree-folder-close-icon',
                loadable: true
            }
        },
        forest: true,
        animateScroll: true,
        height: 18,
        expandTo: true
    },
    initialize: function(options){
        this.setOptions(options);
        Object.append(this, {
            types: this.options.types,
            forest: this.options.forest,
            animateScroll: this.options.animateScroll,
            dfltType: this.options.dfltType,
            height: this.options.height,
            container: document.id(options.container),
            UID: ++CV.Tree.UID,
            key: {},
            expanded: []
        });
        this.defaults = {
            name: '',
            cls: '',
            openIcon: 'cv-tree-empty-icon',
            closeIcon: 'cv-tree-empty-icon',
            loadable: false,
            hidden: false
        };
        this.dfltState = {
            open: false
        };
        this.$index = [];
        this.updateOpenState();
        if(this.options.expandTo) this.initExpandTo();

        this.DOMidPrefix = 'cv-tree-';
        this.wrapper = new Element('div.cv-tree-wrapper').inject(this.container, 'inside');
        this.events();
        this.initScroll();
        this.initSelection();
        this.initHover();

        this.addEvent('drawChildren', function(parent) {
            var nodes = parent._toggle || [];
            nodes.each(function(node) {
                node.drawToggle();
            }, this);
            parent._toggle = [];
        });

        var id = this.options.id;
        this.id = id;

        if(id != null) CV.ids[id] = this;
        if(this.options.initialize) {
            this.options.initialize.call(this);
        }
    },
    events: function() {

        this.wrapper.addEvents({
            'mousemove': this.mouse.bind(this),
            'mouseover': this.mouse.bind(this),
            'mouseout': this.mouse.bind(this),
            'mouseleave': this.mouseleave.bind(this),
            'mousedown': this.mousedown.bind(this),
            'click': this.toggleClick.bind(this),
            'dblclick': this.toggleDblclick.bind(this),
            'selectstart': this.preventDefault.bind(this)
        });
		
        this.container.addEvent('click', this.focus.bind(this));
		
        document.addEvents({
            'click': this.blurOnClick.bind(this),
            'keydown': this.keyDown.bind(this),
            'keyup': this.keyUp.bind(this)
        });
    },
    blurOnClick: function(event) {
        var target = event.target;
        while(target) {
            if(target == this.container) return;
            target = target.parentNode;
        }
        this.blur();
    },
    focus: function() {
        if(CV.Focus && CV.Focus == this) return this;
        if(CV.Focus) CV.Focus.blur();
        CV.Focus = this;
        this.focused = true;
        this.container.addClass('cv-tree-focused');

        return this.fireEvent('focus');
    },
    blur: function(){
        CV.Focus = null;
        if(!this.focused) return this;
        this.focused = false;
        this.container.removeClass('cv-tree-focused');

        return this.fireEvent('blur');
    },
    $getIndex: function(){
        this.$index = [];
        var node = this.forest ? this.root.getFirst() : this.root;
        var previous = node;
        while(node){
            if(!(previous.hidden && previous.contains(node))){
                if(!node.hidden) this.$index.push(node);
                previous = node;
            }
            node = node._getNextVisible();
        }
    },
    preventDefault: function(event){
        event.preventDefault();
    },
    mousedown: function(event){
        if(event.rightClick) return;
        event.preventDefault();

        this.fireEvent('mousedown');
    },
    mouseleave: function(){
        this.mouse.coords = {
            x: null,
            y: null
        };
        this.mouse.target = false;
        this.mouse.node = false;
        if(this.hover) this.hover();
    },
    mouse: function(event) {
        this.mouse.coords = this.getCoords(event);
        var target = this.getTarget(event);
        this.mouse.target = target.target;
        this.mouse.node	= target.node;
    },
    getTarget: function(event){
        var target = event.target, node;
        while(!(/cv-tree/).test(target.className)){
            target = target.parentNode;
        }
        var test = target.className.match(/cv-tree-(gadjet)-[^n]|cv-tree-(icon)|cv-tree-(name)|cv-tree-(checkbox)/);
        if(!test){
            var y = this.mouse.coords.y;
            if(y == -1 || !this.$index) {
                node = false;
            }
            else{
                node = this.$index[((y)/this.height).toInt()];
            }
            return {
                node: node,
                target: 'node'
            };
        }
        for(var i = 5; i > 0; i--){
            if(test[i]) {
                var type = test[i];
                break;
            }
        }
        return {
            node: CV.Tree.Nodes[target.getAttribute('uid')],
            target: type
        };
    },
    getCoords: function(event) {
        var position = this.wrapper.getPosition();
        var x = event.page.x - position.x;
        var y = event.page.y - position.y;
        var wrapper = this.wrapper;
        if((y-wrapper.scrollTop > wrapper.clientHeight) || (x - wrapper.scrollLeft > wrapper.clientWidth)) {//scroll line
            y = -1;
        }
        return { x: x, y: y };
    },
    keyDown: function(evt) {
        this.key = evt;
        this.key.state = 'down';
        if(this.focused) this.fireEvent('keydown', [evt]);
    },
    keyUp: function(evt) {
        this.key = {};
        this.key.state = 'up';
        if(this.focused) this.fireEvent('keyup', [evt]);
    },
    toggleDblclick: function(evt) {
        var target = this.mouse.target;
        if(!(target == 'name' || target == 'icon')) return;
        this.mouse.node.toggle();
    },
    toggleClick: function() {
        if(this.mouse.target != 'gadjet') return;
        this.mouse.node.toggle();
    },
    initScroll: function() {
        this.scroll = new Fx.Scroll(this.wrapper, {
            link: 'cancel'
        });
    },
    scrollTo: function(node){
        var position = node.getVisiblePosition();
        var top = position*this.height;
        var up = (top < this.wrapper.scrollTop);
        var down = (top > (this.wrapper.scrollTop + this.wrapper.clientHeight - this.height));
        if(position == -1 || ( !up && !down ) ) {
            this.scroll.fireEvent('complete');
            return false;
        }
        if(this.animateScroll){
            this.scroll.start(this.wrapper.scrollLeft, top - (down ? this.wrapper.clientHeight - this.height : this.height));
        }
        else{
            this.scroll.set(this.wrapper.scrollLeft, top - (down ? this.wrapper.clientHeight - this.height : this.height));
            this.scroll.fireEvent('complete');
        }
        return this;
    },
    updateOpenState: function() {
        this.addEvents({
            'drawChildren': function(parent) {
                parent.children.each(function(child) {
                    child.updateOpenState();
                }, this);
            },
            'drawRoot': function() {
                this.root.updateOpenState();
            }
        });
    },
    expandTo: function(node) {
        if(!node) return this;
        var path = [];
        while(!node.isRoot() && !(this.forest && node.getParent().isRoot()) ) {
            node = node.getParent();
            if(!node) break;
            path.unshift(node);
        }
        path.each(function(el){
            el.toggle(true);
        });
        return this;
    },
    initExpandTo: function() {
        this.addEvent('loadChildren', function(parent) {
            if(!parent) return;
            var children = parent.children;
            for(var i = children.length; i--; ) {
                var child = children[i];
                if(child.expandTo) this.expanded.push(child);
            }
        });
        function expand() {
            this.expanded.each(function(node) {
                this.expandTo(node);
            }, this);
            this.expanded = [];
        }
        this.addEvents({
            'load':     expand.bind(this),
            'loadNode': expand.bind(this)
        });
    }
});

Array.implement({

    inject: function(added, current, where) {
        var pos = this.indexOf(current) + (where == 'before' ? 0 : 1);
        for(var i = this.length-1; i >= pos; i--) {
            this[i + 1] = this[i];
        }
        this[pos] = added;
        return this;
    }
});
